package roborally;

import java.util.*;
import be.kuleuven.cs.som.annotate.*;

import roborally.Pieces.*;
import roborally.util.*;

/**
 * This is a class for Boards of the Roborally game which has a height, width, maximum height, maximum width, robots, batteries and walls and can be terminated.
 * @invar 	The bidirectional association between Board and this GamePiece with respect to Positions is consistent.
 * 			| foreach GamePiece piece where piece.getPosition().getBoard().equals(this)
 * 			|	this.getItemsAt(piece.getPosition()).contains(piece) || this.getRobotAt(piece.getPosition() ).equals(piece) ||
 * 			|	this.getWallAt(piece.getPosition() ).equals(piece)
 * @invar	A board does not contain terminated GamePieces
 * 			| foreach GamePiece piece where piece.getPosition().getBoard().equals(this)
 * 			|	!piece.isTerminated()
 * @version 3.0
 * @author Bart Opsomer 2de Bachelor Ingenieurswetenschappen Electrotechniek-Computerwetenschappen 
 * 		   Peter Opsomer 1ste Master Wiskundige IngenieursTechnieken
 */
public class Board {
	// All the robots on the board
	private Map<Position,Robot> robots;
	// All items on the board that can be picked up by a robot
	private Map<Position,Set<Item>> items;
	// All the walls on the board
	private Map<Position,Wall> walls;
	
	// The height of the board
	private final long height;
	// The width of the board
	private final long width;

	private boolean terminated;
	
	// the maximum height and width of all boards
	private static final long maxHeight = Long.MAX_VALUE;
	private static final long maxWidth = Long.MAX_VALUE;
	
	/**
	 * Creates a new Board with the given width and height and initializes the fields robots, 
	 * carriablesOnBoard and walls.
	 * 
	 * @param	width the width of the board
	 * @param 	height the height of the board
	 * @post	| getRobots() != null && getCarriables() != null && getWalls() != null && !isTerminated()
	 * @throws 	IllegalArgumentException
	 * 			when the given width and height are not valid dimensions for a board
	 * 			| !isValidDimension(width,height)
	 */
	public Board(long width, long height) throws IllegalArgumentException {
		if(isValidDimension(width,height)) {
			this.height = height;
			this.width = width;
		}
		else{
			throw new IllegalArgumentException();
		}
		robots = new HashMap<Position,Robot>();
		items =  new HashMap<Position,Set<Item>>();
		walls =  new HashMap<Position,Wall>();
		this.terminated =false;
	}
	/**
	 * @return return whether this board is terminated
	 */
	@Basic
	public boolean isTerminated() {
		return this.terminated;
	}
	/**
	 * Terminates all the robots, item and walls on the board and the board itself.
	 * @post	the maps robots, walls and carriablesOnBoard are empty
	 * 			| new.robots.isEmpty() == true
	 * 			| new.walls.isEmpty() == true
	 * 			| new.getCarriablesOnBoard.isEmpty() == true
	 * @post	this board is terminated
	 * 			| new.isTerminated() == true
	 * @post	all gamePieces on the board are terminated
	 * 			| forall items in this.getCarriablesOnBoard()
	 * 			|	(new item).isTerminated() == true
	 * 			| forall wall in this.getWalls()
	 * 			|	(new wall).isTerminated() == true
	 * 			| forall robot in this.getRobots()
	 * 			|	(new robot).isTerminated() == true
	 */
	public void terminate() {
		// Terminate all Robots, Walls and Batteries
		for (Item item : getAllItems()) {
			item.terminate();
		}
		for (Robot robot : getRobots()) {
			robot.terminate();
		}
		for (Wall wall : getWalls()) {
			wall.terminate();
		}
		// make sure the maps robots, walls and carriablesOnBoard are empty
		robots.clear();
		walls.clear();
		items.clear();
		/* First set this to true so that in the terminate()-method of all 
		 GamePieces on this Board we don't remove the GamePiece, but not necessary
		 */
		this.terminated = true;
	}
	// 
	/**
	 * Add the Item on the board on the given position if possible.
	 * 
	 * @param position 	the position on which the item is to be placed
	 * @param item		the item to be added
	 * @post			if item or this is terminated, nothing changes
	 * 					| this.isTerminated()  || item.isTerminated()
	 * 					| then  new = this
	 * @post			the board contains the item
	 * 					| new.getCarriablesOnBoard().contains(item)
	 * @throws 			IllegalArgumentException
	 * 					it is not possible to add an item on the position
	 * 					| walls.containsKey(position) || getItemsAt(position).contains(item)
	 * @throws 			NullPointerException
	 * 					item or position are not effective
	 * 					| position == null || item == null
	 */
	public void addItem(Position position, Item item) throws IllegalArgumentException{
		if(position == null) {
			throw new NullPointerException();
		}
		if(!isTerminated()  && !item.isTerminated()) {
			if(items.containsKey(position)) {
				// Also HashSet.contains works in constant time
				if(items.get(position).contains(item)) {
					throw new IllegalArgumentException("Same Item cannot be added twice");
				}
				item.setPosition(position);
				items.get(position).add(item);
			}
			else if(walls.containsKey(position)) {
				throw new IllegalArgumentException();
			}
			else {
				Set<Item> set = new HashSet<Item>();
				set.add(item);
				items.put(position, set);
				item.setPosition(position);
			}
		}
	}
	/**
	 * Removes the item from the board.
	 * 
	 * @param 	item the item to be removed from the board
	 * @post	the item is removed
	 * 			| !new.getCarriablesOnBoard.contains(item)
	 * @throws	IllegalStateException
	 * 			when the item is not on this board
	 * 			| !carriablesOnBoard.containsKey(item.getPosition() )
	 * @throws 	NullPointerException
	 * 			item is not effective
	 * 			| item == null
	 */
	public void removeItem(Item item) {
		Position pos = item.getPosition();
		if (items.containsKey(pos) && items.get(pos).contains(item) ) {
			items.get(pos).remove(item);
			item.setPosition(null);
		}
		else{
			throw new IllegalStateException("Item was not on Board.");
		}
	}
	/**
	 * Returns all the batteries located on the board, not in constant time.
	 * @return  if this is terminated return null
	 * 			| if( this.isTerminated() )
	 * 			| 	then result == null
	 * @return 	the Batteries on the board
	 * 			| foreach Battery battery
	 * 			|	if battery.getPosition().getBoard() == this 
	 * 			|		result.contains(battery)
	 */
	public Set<Battery> getBatteries(){
		if(!isTerminated()) {
			Set<Battery> result = new HashSet<Battery>();
			for(Set<Item> setOfItems : items.values()) {
					for(Item item : setOfItems) {
						if(item instanceof Battery)
							result.add((Battery)item);
					}
			}
			return result;
		}
		return null;
	}
	/**
	 * Returns all the Repairkits located on the board, not in constant time
	 * @return  if this is terminated return null
	 * 			| if( this.isTerminated() )
	 * 			| 	then result == null
	 * @return 	the repairkits on the board
	 * 			| foreach RepairKit repairKit
	 * 			|	if repairKit.getPosition().getBoard() == this 
	 * 			|		result.contains(repairKit)
	 */
	public Set<RepairKit> getRepairKits(){
		if(!isTerminated()) {
			Set<RepairKit> result = new HashSet<RepairKit>();
			for(Set<Item> setOfItems : items.values()) {
					for(Item item : setOfItems) {
						if(item instanceof RepairKit)
							result.add((RepairKit)item);
					}
			}
			return result;
		}
		return null;
	}
	/**
	 * Returns all the surpriseBoxes located on the board, not in constant time.
	 * @return  if this is terminated return null
	 * 			| if( this.isTerminated() )
	 * 			| 	then result == null
	 * @return 	the SurpriseBoxes on the board
	 * 			| foreach SurpriseBox surpriseBox
	 * 			|	if surpriseBox.getPosition().getBoard() == this 
	 * 			|		result.contains(surpriseBox)
	 */
	public Set<SurpriseBox> getSupriseBoxes(){
		if(!isTerminated()) {
			Set<SurpriseBox> result = new HashSet<SurpriseBox>();
			for(Set<Item> setOfItems : items.values()) {
					for(Item item : setOfItems) {
						if(item instanceof SurpriseBox)
							result.add((SurpriseBox)item);
					}
			}
			return result;
		}
		return null;
	}
	/**
	 * Returns all items on the board, linear in the number of positions
	 * @return  if this is terminated return null
	 * 			| if( this.isTerminated() )
	 * 			| 	then result == null
	 * @return 	the Items on the board
	 * 			| foreach Item item
	 * 			|	if item.getPosition().getBoard() == this 
	 * 			|		result.contains(item)
	 */
	@Basic
	public Set<Item> getAllItems(){
		if(!isTerminated()) {
			Set<Item> result = new HashSet<Item>();
			for(Set<Item> set : items.values()) {
				result.addAll(set);
			}
			return result;
		}
		return null;
	}
	/**
	 * Return all items located at the given Position.
	 * @param position the position to look at
	 * @return  if this is terminated return null
	 * 			| if( this.isTerminated() )
	 * 			| 	then result == null
	 * @return 	null if there is are no Items at the position
	 * 			| result = null
	 * 			| if !exists Item item
	 * 			|	where item.getPosition() == position
	 * @return 	all items located at the given Position if it is effective
	 * 			| if(position != null) 
	 * 			| 	foreach Item item in result
	 * 			|		item.getPosition().equals(position)
	 * 			| 	!exists Item item where
	 * 			|		item.getPosition.equals(position) && 
	 * 			|		!result.contains(item)
	 */
	public Set<Item> getItemsAt(Position position) {
		if(isTerminated())
			return null;
		return items.get(position);
	}
	/**
	 * Returns the robot on the given position.
	 * @return  if this is terminated return null
	 * 			| if( this.isTerminated() )
	 * 			| 	then result == null
	 * @return 	null if there is no Robot at the position
	 * 			| result = null
	 * 			| if !exists Robot robot
	 * 			|	where robot.getPosition() == position
	 * @return 	the robot on the given position
	 * 			| result = Robot robot
	 * 			|	where robot.getPosition() == position
	 */
	public Robot getRobotAt(Position position) {
		if(isTerminated())
			return null;
		return robots.get(position);
	}
	/**
	 * Adds the robot to the board if possible.
	 * @param 	robot	the robot to be added
	 * @param   pwo	    the PositionWithOrientation to add the Robot to
	 * @post	The board contains the robot if there was no wall and another robot on that position
	 * 			and this board is not terminated and the robot is not terminated
	 * 			| if( !isTerminated() && !robot.isTerminated() && 
	 * 			| !hasWallAt(pwo.getPosition() ) && !hasRobotAt(pwo.getPosition() )
	 * 			| 	then new.getRobots().contains(robot) == true
	 * 			| else
	 * 			|	new == this	
	 * @throws  IllegalArgumentException
	 * 			when there is already a wall or another robot on that position
	 * 			| if ( !isTerminated() && !robot.isTerminated() && (hasWallAt(pwo.getPosition() ) || hasRobotAt(pwo.getPosition() )) )
	 * @throws  IllegalArgumentException
	 * 			when pwo is not effective
	 * 			| if ( !isTerminated() && !robot.isTerminated() && pwo == null)
	 * @throws 	NullPointerException
	 * 			robot is not effective
	 * 			| robot == null
	 * 
	 */
	public void addRobot(Robot robot,PositionWithOrientation pwo) throws IllegalArgumentException, NullPointerException {
		// throws IllegalArgumentException if the position contains either a wall or a robot
		if(!isTerminated()  && !robot.isTerminated()) {
			if(pwo == null) {
				throw new IllegalArgumentException();
			}
			if(robots.containsKey(pwo.getPosition() )) {
				// Also HashSet.contains works in constant time
				if(robots.get(pwo.getPosition() ).equals(robot) ) {
					throw new IllegalArgumentException("Same Robot cannot be added twice");
				}
				else{
					throw new IllegalArgumentException("No two Robots on same location");
				}
			}
			else if(walls.containsKey(pwo.getPosition() ) ) {
				throw new IllegalArgumentException("No Robot on a Wall");
			}
			else {
				robot.setPositionWithOrientation(pwo);
				robots.put(pwo.getPosition() ,robot);
			}
		}
	}
	/**
	 * Remove the robot from the board.
	 * @param robot the robot to be removed from the board 
	 * @post	the robot is not on the board
	 * 			| new.getRobots().contains(robot.getPosition() ) == false
	 * @throws  IllegalStateException
	 * 			The Robot was not on the Board or not on the Position stored in robot
	 * 			|  !hasRobotAt(robot.getPosition() ) || 
	 * 			|	getRobotAt(robot.getPosition() ) != robot
	 * @throws 	NullPointerException
	 * 			robot is not effective
	 * 			| robot == null
	 */
	public void removeRobot(Robot robot) throws NullPointerException{
		Position position = robot.getPosition();
		if(robots.containsKey(position) && robots.get(position).equals(robot)) {
			robots.remove(position);
			robot.setPositionWithOrientation(null);
		}
		else{
			throw new IllegalStateException("The Robot was not on the Board or not on the Position stored in robot");
		}
	}
	/**
	 * Returns true if the given position contains a robot.
	 * @param position the position in question
	 * @return  if this is terminated return false
	 * 			| if( this.isTerminated() )
	 * 			| 	then result == false
	 * @return 	returns if the position on the board contains a robot
	 * 			| result = exists Robot robot where
	 * 			|	getRobots().contains(robot) && robot.getPosition().equals(position)
	 */
	public boolean hasRobotAt(Position position) {
		if(isTerminated())
			return false;
		return robots.containsKey(position);
	}
	/**
	 * Returns all robots on the on the board.
	 * @return  if this is terminated return null
	 * 			| if( this.isTerminated() )
	 * 			| 	then result == null
	 * @return	|result = Set<Robot> robots
	 * 			|	where foreach robot robots.contains(robot)
	 *  		|		where robot.getPosition.getBoard() == this
	 */
	@Basic
	public Set<Robot> getRobots(){
		if(!isTerminated()) {
			return new HashSet<Robot>(robots.values());
		}
		return null;
	}
	/**
	 * Returns whether the given dimensions are valid. Dimensions 0, maxWidth and maxHeight are included as
	 * requested so it is possible to have a Board with only one Position (0,0).
	 * @param w the possible width
	 * @param h the possible height
	 * @return  if this is terminated return false
	 * 			| if( this.isTerminated() )
	 * 			| 	then result == false
	 * @return returns true if 0<=w<= maxWidth and 0<=h<= maxHeight else false
	 * 			| result = ((w <= maxWidth) && (w>=0) ) &&
				| 	((h <= maxHeight) && (h>=0))
	 */
	@Model
	private boolean isValidDimension(long w, long h) {
		if(!isTerminated()) {
			return ((w <= maxWidth) && (w>=0) ) &&
				((h <= maxHeight) && (h>=0));
		}
		return false;
	}
	/**
	 * @return the height of this board
	 */
	@Basic @Immutable
	public long getHeight() {
		return height;
	}
	/**
	 * @return the width of this board
	 */
	@Basic @Immutable
	public long getWidth() {
		return width;
	}
	/**
	 * Get all robots on the on the board.
	 * @return  if this is terminated return null
	 * 			| if( this.isTerminated() )
	 * 			| 	then result == null
	 * @return	|result = Set<Wall> walls
	 * 			|	where foreach wall walls.contains(wall)
	 *  		|		where wall.getPosition().getBoard() == this
	 */
	@Basic
	public Set<Wall> getWalls() {
		if(!isTerminated()) {
			return new HashSet<Wall>(walls.values());
		}
		return null;
	}
	/**
	 * Adds a wall to the Board if possible, the corresponding position will be set in the Wall too.
	 * @param wall	the Wall to be added
	 * @param position the position to add the Wall to.
	 * @post	The board contains the wall if there was no wall and another robot on that position
	 * 			and this board is not terminated and the wall is not terminated
	 * 			| if( !isTerminated() && !robot.isTerminated() && 
	 * 			| !hasWallAt(wall.getPosition()) && !hasRobotAt(wall.getPosition() )
	 * 			| 	then new.getWalls().contains(wall) == true
	 * @throws 	NullPointerException
	 * 			wall is not effective
	 * 			| wall == null
	 * @throws  IllegalArgumentException
	 * 			when there is already a wall, item or a robot on that position
	 * 			| if (!isTerminated() && !wall.isTerminated() && (hasWallAt(wall.getPosition()) || hasRobotAt(wall.getPosition() )) ) )
	 * @throws  IllegalArgumentException
	 * 			when position is not effective
	 * 			| if (!isTerminated() && !wall.isTerminated() && position == null)
	 */
	public void addWall(Wall wall,Position position) throws IllegalArgumentException, NullPointerException {
		// throws IllegalArgumentException if the position contains either a wall or a robot or an carriable item
		if(!isTerminated() && !wall.isTerminated()) {
			if(position == null) {
				throw new IllegalArgumentException("No effective position");
			}
			if(walls.containsKey(position) || robots.containsKey(position) || items.containsKey(position)) {
				throw new IllegalArgumentException();
			}
			wall.setPosition(position);
			this.walls.put(position, wall);
		}
	}
	/**
	 * Returns the wall on the given position.
	 * @return  if this is terminated return null
	 * 			| if( this.isTerminated() )
	 * 			| 	then result == null
	 * @return 	null if there is no wall at the position
	 * 			| result = null
	 * 			| if !exists Wall wall
	 * 			|	where wall.getPosition() == position
	 * @return 	the wall on the given position
	 * 			| result = Wall wall
	 * 			|	where wall.getPosition() == position
	 */
	public Wall getWallAt(Position position) {
		if(isTerminated())
			return null;
		return walls.get(position);
	}
	/**
	 * Returns true if the given position contains a wall.
	 * @param position the position in question
	 * @return  if this is terminated return false
	 * 			| if( this.isTerminated() )
	 * 			| 	then result == false
	 * @return 	returns whether the position on the board contains a wall
	 * 			| result = exists Wall wall where
	 * 			|	getWalls().contains(wall) && wall.getPosition().equals(position)
	 */
	public boolean hasWallAt(Position position) {
		if(isTerminated())
			return false;
		return walls.containsKey(position);
	}
	/**
	 * Remove the wall from the board.
	 * @param wall the wall to be removed from the board 
	 * @post	the wall is not on the board
	 * 			| new.getWalls().contains(wall.getPosition()) == false
	 * @post	the wall has no Position any more
	 * 			| (new wall).getPosition() == null
	 * @throws 	NullPointerException
	 * 			wall is not effective
	 * 			| wall == null
	 */
	public void removeWall(Wall wall) throws NullPointerException {
		Position position = wall.getPosition();
		if(walls.containsKey(position)) {
			walls.remove(position);
			wall.setPosition(null);
		}
	}
	/**
	 * Returns if the robot can move 1 step forward, disregarding Energy.
	 * @param robot the to be moved
	 * @return  if this is terminated return false
	 * 			| if( this.isTerminated() )
	 * 			| 	then result == false
	 * @return 	| let 
	 * 			|	nextPositionOfRobot = robot.getPositionWithOrientation().nextPosition() 
	 * 			| in
	 * 			| if(!isValidPosition(nextPositionOfRobot) then result == false
	 * 			| if(hasRobotAt(nextPositionOfRobot.getPosition()) || hasWallAt(nextPositionOfRobot.getPosition() ) )
	 * 			| 	then result = false
	 * 			| else
	 * 			|	result = true
	 * @throws 	NullPointerException
	 * 			robot is not effective
	 * 			| robot == null
	 */
	@Model
	private boolean canMove(Robot robot) throws NullPointerException {
		if(isTerminated())
			return false;
		boolean authorised = true;
		PositionWithOrientation nextPositionOfRobot;
		try{
			nextPositionOfRobot = robot.getPositionWithOrientation().nextPosition();
		} catch(MoveOutOfBoardException outOfBoard) {
			return false;
		}
		if(robots.containsKey(nextPositionOfRobot.getPosition()) || walls.containsKey(nextPositionOfRobot.getPosition() ) ) {
			authorised =false;
		}
		return authorised;
	}
	/**
	 * Moves the robot 1 step forward.
	 * @pre 	The Robot has sufficient energy to move 
	 * 			| myRobot.getEnergy().isGreaterThanOrEquals(myRobot.getMoveEnergy() )
	 * @param 	myRobot the robot to be moved
	 * @post	if the robot can move, it is moved 1 step forward
	 * 			| if(canMove(myRobot))
	 * 			| 	then (new myRobot).getPosition() = myRobot.getPositionWithOrientation().nextPosition() &&
	 * 			|		new.getRobotAt(myRobot.getPositionWithOrientation().nextPosition()) == new myRobot
	 * @throws 	NullPointerException
	 * 			myRobot is not effective
	 * 			| myRobot == null
	 */
	public void moveRobot(Robot myRobot) throws NullPointerException {
		assert(myRobot.getEnergy().isGreaterThanOrEquals(myRobot.getMoveEnergy()));
		if(robots.containsValue(myRobot)) {
			if(canMove(myRobot)) {
				robots.remove(myRobot.getPosition() );
				myRobot.setNextPositionAndUseEnergy();
				Position pos = myRobot.getPosition();
				robots.put(pos, myRobot);
			}
		}
		
	}
	/**
	 * Returns true if the given position contains a gamePiece.
	 * @param position the position in question
	 * @return  if this is terminated or position is null return true
	 * 			| if( this.isTerminated() || position == null)
	 * 			| 	then result == true
	 * @return  returns true if a gamePiece has the given position
	 * 			| if (exists GamePiece piece )
	 * 			|		where piece.getPosition() == position
	 * 			| 	then result == true
	 */
	public boolean isOccupiedPosition(Position position) {
		if(isTerminated())
			return true;
		if(position == null)
			return true;
		if(robots.containsKey(position) || walls.containsKey(position) )
			return true;
		if(items.containsKey(position)) 
			return !items.get(position).isEmpty();
		return false;
	}
	/**
	 * On the closest occupied position to the robot in the orientation of the robot, a random gamePiece is hit.
	 * 
	 * @param myRobot the robot that shoots
	 * @pre 	The Robot has sufficient energy to shoot 
	 * 			| myRobot.getEnergy().isGreaterThanOrEquals(myRobot.getShootEnergy() )
	 * @post  	if this is terminated nothing changes
	 * 			| if( this.isTerminated() )
	 * 			| 	then new == this
	 * @effect	the closest occupied position to the robot in the orientation of the robot, has been shot
	 * 			| let 
	 * 			|	targetPosition = the first position,starting form targetPosition.getPosition(), where isOccupiedPosition(position) &&
	 * 			|		targetPosition.getOrientation() = targetPosition.getPosition().getOrientation()
	 * 			|	List pieces1 =  a list of GamePieces on the targetPosition before the method
	 * 			| in
	 * 			| exists GamePiece piece where
	 * 			|		pieces1.contains(piece);
	 * 			|		piece.shootThis();
	 * 			|		piece.getPosition() == targetPosition;
	 * @throws 	NullPointerException
	 * 			myRobot is not effective
	 * 			| myRobot == null
	 */
	public void shoot(Robot myRobot) throws NullPointerException {
		assert(myRobot.getEnergy().isGreaterThanOrEquals(myRobot.getShootEnergy()));
		if(isTerminated() || myRobot.isTerminated() )
			return;
		Set<GamePiece> pieces = getShootablePieces(myRobot);
		myRobot.useEnergy(myRobot.getShootEnergy());
		if(pieces.isEmpty())
			return;
		// store the shootable pieces in an array
		Random rand = new Random();
		GamePiece[] arrayOfGamePieces = new GamePiece[pieces.size()];
		arrayOfGamePieces = pieces.toArray(arrayOfGamePieces);
		// select a random piece of the shootable pieces
		int nextIntRand = rand.nextInt(arrayOfGamePieces.length);
		GamePiece pieceShot = arrayOfGamePieces[nextIntRand];
		// shoot the random piece
		pieceShot.shootThis();
	}
	/**
	 * Returns whether a robot is in range of the given robot, i.e. if myRobot.shoot can hit an other robot.
	 * @param myRobot the robot to test whether it has another robot in range
	 * @return 	returns whether a robot is in range of the given robot
	 * 			| let
	 * 			|	aRobot, a Robot where aRobot.getPosition().getBoard() == myRobot.getPosition().getBoard()
	 * 			| in
	 * 			|	if exists aRobot where getShootablePieces(myRobot).contains(aRobot)
	 * 			| 	then result = true
	 * @throws 	NullPointerException
	 * 			myRobot is not effective
	 * 			| myRobot == null
	 */
	public boolean robotInRangeOf(Robot myRobot) throws NullPointerException {
		Set<GamePiece> pieces = getShootablePieces(myRobot);
		boolean robotInRange = false;
		for(Robot robot : getRobots()) {
			if(pieces.contains(robot)) {
				robotInRange = true;
			}
		}
		return robotInRange;
	}
	/**
	 * Return the GamePieces that this Robot can shoot. This method was extracted from shoot(Robot) because it is needed for testing condition
	 * can-hit-robot in Program.
	 * 
	 * @param myRobot the Robot that shoots
	 * @return	the objects on the closest occupied position to the robot in the orientation of the robot
	 * 			| let 
	 * 			|	targetPosition = the first position,starting form targetPosition.getPosition(), where isOccupiedPosition(position) &&
	 * 			|		targetPosition.getOrientation() = targetPosition.getPosition().getOrientation()
	 * 			| in
	 * 			| result.containsAll(this.getItemsAt(targetPosition)   )
	 * 			| result.containsAll(this.getRobotAt(targetPosition) )
	 * 			| result.containsAll(this.getWallAt(targetPosition) )
	 * 			| !result.contains(null)
	 * @throws 	NullPointerException
	 * 			myRobot is not effective
	 * 			| myRobot == null
	 */
	@Model
	private Set<GamePiece> getShootablePieces(Robot myRobot)  throws NullPointerException
	{
		PositionWithOrientation targetPosition = myRobot.getPositionWithOrientation();
		do {
			try {
				targetPosition = targetPosition.nextPosition();
			}
			catch (MoveOutOfBoardException exc) {
				// there is no object in between the robot and the edge of the board, so return empty Set
				return new HashSet<GamePiece>();
			}
		}
		while(!isOccupiedPosition(targetPosition.getPosition() ) );
		return getGamePiecesAt(targetPosition.getPosition() );
	}
	/**
	 * With this method, it is possible to get all GamePieces located on a certain Position in constant time.
	 * @param 	position the Position where the GamePieces are
	 * @return 	All GamePieces located on a certain Position
	 * 			| 	result.contains(getItemsAt(position) ) && result.contains(getRobotAt(position) ) &&
	 * 			|	result.contains(getWallAt(position) ) && !result.contains(null)
	 */
	public Set<GamePiece> getGamePiecesAt(Position position) {
		Set<GamePiece> pieces = new HashSet<GamePiece>();
		if(walls.containsKey(position)) {
			pieces.add(walls.get(position));
		}
		if(robots.containsKey(position)) {
			pieces.add(robots.get(position));
		}
		if(items.containsKey(position)) {
			pieces.addAll(items.get(position));
		}
		return pieces;
	}
	/**
	 * Put all GamePieces of secondBoard on this board on their corresponding place, if possible.
	 * @param secondBoard the board to be merged into this board
	 * @post	if this or secondBoard is terminated return null
	 * 			| if( this.isTerminated() || secondBoard.isTerminated() || secondBoard == this)
	 * 			| 	then new == this && new secondBoard == secondBoard
	 * @post	all GamePieces of secondBoard are put on this board on their corresponding place, if possible
	 * 			| foreach piece on secondBoard
	 * 			|	where piece.setBoard(this) is valid
	 * 			| new.contains(piece) &&
	 * 			| new secondBoard.isTerminated()
	 * @throws	NullPointerException
	 * 			SecondBoard is not effective
	 * 			| secondBoard == null
	 */
	public void merge(Board secondBoard) throws NullPointerException {
		if(isTerminated() || secondBoard.isTerminated())
			return;
		if(secondBoard == this)
			return;
		for(Item item : secondBoard.getAllItems()) {
			try {
				Position position = item.getPosition().getSamePositionOnOtherBoard(this);
				secondBoard.removeItem(item);
				// item.setPosition(position); this.addItem(item.getPosition(),item); is not needed because addItem does this 
				// and gives problems when this Board is too small: then second statement throws Exception so item.terminate() is called
				// but the new Board is already set via setPosition() so item tries to remove itself from new Board while not added (yet)
				this.addItem(position,item);
			}
			catch(Exception e){
				// a wall is on the corresponding place on this or invalid new position, so terminate!
				item.terminate();
			}
		}
		for(Robot robot : secondBoard.getRobots()) {
			try {
				Position pos = robot.getPosition().getSamePositionOnOtherBoard(this);
				PositionWithOrientation positionwO = PositionWithOrientation.getPWO(pos.getX(), pos.getY(), pos.getBoard(), robot.getPositionWithOrientation().getOrientation());
				secondBoard.removeRobot(robot);
				this.addRobot(robot,positionwO);
			}
			catch(Exception e){
				robot.terminate();
			}
		}
		for (Wall wall : secondBoard.getWalls()) {
			try {
				Position position = wall.getPosition().getSamePositionOnOtherBoard(this);
				secondBoard.removeWall(wall);
				addWall(wall,position);
			}
			catch(Exception e){
				// a wall or a robot is on the corresponding place on this ,so terminate!
				wall.terminate();
			}
		}
		// Walls, Robots or Items that were not added to this Board should have been terminated by now, so that we don't 
		// have to call secondBoard.terminate(); which would also terminate Robots etc that were effectively moved
		secondBoard.terminated = true;
	}
	/**
	 * Add walls on the diagonal from (x1,y1) to (x2,y2).
	 * @param x1 the x-axis coordinate of the first point
	 * @param x2 the x-axis coordinate of the second point
	 * @param y1 the y-axis coordinate of the first point
	 * @param y2 the y-axis coordinate of the second point
	 * @post 	if this board is terminated, nothing changes
	 * 			| if(isTerminated())
	 * 			|	then new == this
	 * @post 	walls were added on the diagonal from (x1,y1) to (x2,y2)
	 * 			| let y = y1 if x1 <= x2, else y2
	 * 			| for i in [0, abs(x1-x2) ] : new.hasWallAt(Position.getPosition(min(x1,x2)+i, y - i,this) )
	 * @throws 	IllegalArgumentException
	 * 			the given positions do not form a diagonal
	 * 			| abs(x2-x1) != abs(y2-y1)
	 */
	public void addDiagonalWalls(long x1,long x2,long y1, long y2) {
		if(isTerminated())
			return;
		if(Math.abs(x2-x1) != Math.abs(y2-y1) ) {
			throw new IllegalArgumentException("No diagonal was given.");
		}
		long toNextX = -1;
		if(x1 < x2) {
			toNextX = 1;
		}
		long y = y1;
		long toNextY = -1;
		if(y1 < y2) {
			toNextY = 1;
		}
		// toNextX*(x2-x) >= 0 so that actually x2 >= x if x1 < x2 with x increasing  and x2 <= x if x1 >= x2  with x decreasing
		for(long x = x1; toNextX*(x2-x) >= 0; x += toNextX) {
			Wall nextWallToPlace = new Wall();
			addWall(nextWallToPlace,Position.getPosition(x,y,this)); // assume no wall is here yet
			y = y + toNextY;
		}
	}
	/**
	 * Explosion on the given position. The effect of the explosion if the same as being hit 
	 * by a laser. This method is not used when a surprise box explodes when being used by
	 * a Robot, but when a surprise box that is not picked up yet is hit by a laser. Only GamePieces
	 * on the given Position are affected, so this method should be called by SurpriseBox on every
	 * Position next to its own Position. 
	 * 
	 * @param position the position on which the explosion has effect
	 * @effect	the pieces on the given position have experienced the same effect as being shot
	 * 			| foreach GamePiece piece where piece.getPosition() == position
	 * 			|	(new piece) == piece.shootThis()
	 */
	public void explosionAt(Position position) {
		final Position targetPosition = position;
		if(!isOccupiedPosition(targetPosition)) {
			return;
		}
		Set<GamePiece> pieces = new HashSet<GamePiece>();

		if(walls.containsKey(targetPosition)) {
			pieces.add(walls.get(targetPosition));
		}
		if(robots.containsKey(targetPosition)) {
			pieces.add(robots.get(targetPosition));
		}
		if(items.containsKey(targetPosition)) {
			pieces.addAll(items.get(targetPosition));
		}
		for(GamePiece gamePiece : pieces) {
			gamePiece.shootThis();
		}
	}
	/**
	 * Returns an iterator that returns all pieces of this Board that satisfy the condition of the BoardIterator
	 * @return	result = iterator.initiate(this);
	 * @note 	For example: 
	 * 			BoardIterator iterator = myBoard.iterator(new BoardIterator() {
	 * 				public boolean satisfiesCondition(GamePiece piece) {
	 * 					return piece instanceof Energizerable && 
	 * 						( (Energizerable) piece).getEnergy().getAmountInWattSeconds() >= 1000;
	 * 				}
	 * 			}); 
	 * @throws 	NullPointerException
	 * 			iterator is not effective
	 * 			| iterator == null
	 */
	public BoardIterator iterator(BoardIterator iterator) throws NullPointerException {
		iterator.initiate(this);
		return iterator;
	}
}
